package w.z.x.framework.repository.factory;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import w.z.x.framework.repository.interfaces.Repository;
import w.z.x.framework.repository.interfaces.SerialFunction;
import w.z.x.framework.repository.structures.BeanHelper;
import w.z.x.framework.repository.structures.Page;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * MongodbFactory
 *
 * @author 魏祖潇
 */
@Slf4j
@ConditionalOnClass(name = "org.springframework.data.mongodb.core.MongoTemplate")
public class MongodbFactory implements Repository {

    private static final String ID = "_id";

    private static MongoTemplate mongoTemplate;

    @Resource
    private void initTemplate(final MongoTemplate mongoTemplate) {
        MongodbFactory.mongoTemplate = mongoTemplate;
    }

    /**
     * 构建
     *
     * @param clazz 类型
     * @param <E>   泛型
     * @return 实例
     */
    @SneakyThrows
    public static <E extends MongodbFactory> E build(Class<E> clazz) {
        return clazz.newInstance();
    }

    /**
     * 设置属性
     *
     * @param field 字段
     * @param value 值
     * @param <E>   泛型
     * @return 实例
     */
    @SneakyThrows
    public <E extends MongodbFactory> E of(SerialFunction<E, ?> field, Object value) {
        BeanHelper helper = new BeanHelper(this.getClass());
        Collection<Method> methods = helper.getMethods();
        Optional<Method> opt = methods.stream()
                .filter(m -> m.getName().equals("set" + BeanHelper.toFirstUpperCase(SerialFunction.field(field).getName())))
                .findFirst();
        if (!opt.isPresent()) {
            throw new RuntimeException("无法操作内部属性");
        }
        opt.get().setAccessible(true);
        opt.get().invoke(this, value);
        return (E) this;
    }

    /**
     * 获取
     *
     * @param toConvertClass 类型
     * @param id             ID
     * @param <E>            泛型
     * @return 实例
     */
    public static <E extends MongodbFactory> E get(Class<E> toConvertClass, Object id) {
        Query query = Query.query(Criteria.where(ID).is(id));
        return mongoTemplate.findOne(query, toConvertClass);
    }

    /**
     * 获取
     *
     * @param toConvertClass 类型
     * @param query          查询
     * @param <E>            泛型
     * @return 结果
     */
    public static <E extends MongodbFactory> E get(Class<E> toConvertClass, Query query) {
        return mongoTemplate.findOne(query, toConvertClass);
    }

    /**
     * 获取
     *
     * @param toConvertClass 类型
     * @param id             id
     * @param <E>            泛型
     * @return 结果
     */
    public static <E extends MongodbFactory> List<E> get(Class<E> toConvertClass, Object... id) {
        Query query = Query.query(Criteria.where(ID).in(id));
        return mongoTemplate.find(query, toConvertClass);
    }

    /**
     * 列表
     *
     * @param toConvertClass 类型
     * @param query          查询
     * @param <E>            泛型
     * @return 结果
     */
    public static <E extends MongodbFactory> List<E> find(Class<E> toConvertClass, Query query) {
        return mongoTemplate.find(query, toConvertClass);
    }

    /**
     * count
     *
     * @param toConvertClass 类型
     * @param <E>            泛型
     * @return 数量
     */
    public static <E extends MongodbFactory> Long count(Class<E> toConvertClass) {
        return mongoTemplate.count(new Query(), toConvertClass);
    }

    /**
     * count
     *
     * @param toConvertClass 类型
     * @param query          查询
     * @param <E>            泛型
     * @return 数量
     */
    public static <E extends MongodbFactory> Long count(Class<E> toConvertClass, Query query) {
        return mongoTemplate.count(query, toConvertClass);
    }

    public static <E extends MongodbFactory> Page<E> page(Class<E> toConvertClass, Query query, int current, int size) {
        Long total = count(toConvertClass, query);
        query.skip((current - 1) * size).limit(size);
        List<E> rows = find(toConvertClass, query);
        return Page.convert(current, size, total, rows);
    }

    @Override
    public boolean save() {
        mongoTemplate.save(this);
        return true;
    }

    @Override
    public boolean update() {
       BeanHelper helper = new BeanHelper(this.getClass());
       Collection<Field> fields = helper.getFields();
       Optional<Field> id = fields.stream().filter(f -> f.isAnnotationPresent(Id.class)).findFirst();
       if (!id.isPresent()) {
           throw new RuntimeException("未设置Id");
       }
       Map<Field, Object> map = helper.columns(this);
       Optional<Object> myId = map.entrySet().stream()
               .filter(kv -> kv.getKey().equals(id.get()))
               .map(kv -> kv.getValue())
               .findFirst();
       if (!myId.isPresent()) {
           return false;
       }
       Query query = Query.query(Criteria.where(ID).is(myId.get()));
       if (!mongoTemplate.exists(query, this.getClass())) {
           return false;
       }
       mongoTemplate.save(this);
       return true;
    }

    @Override
    public boolean saveOrUpdate() {
        mongoTemplate.save(this);
        return true;
    }

    @Override
    public boolean delete() {
        BeanHelper helper = new BeanHelper(this.getClass());
        Collection<Field> fields = helper.getFields();
        Optional<Field> id = fields.stream().filter(f -> f.isAnnotationPresent(Id.class)).findFirst();
        if (!id.isPresent()) {
            throw new RuntimeException("未设置Id");
        }
        Map<Field, Object> map = helper.columns(this);
        Optional<Object> myId = map.entrySet().stream()
                .filter(kv -> kv.getKey().equals(id.get()))
                .map(kv -> kv.getValue())
                .findFirst();
        if (!myId.isPresent()) {
            return false;
        }
        Query query = Query.query(Criteria.where(ID).is(myId.get()));
        mongoTemplate.remove(query, this.getClass());
        return true;
    }
}
